package ecscore

import (
	"reflect"
	"time"
)

// / Simulation and Simulation is cross fielding
// / Simulation is compatiable with System and SystemExecutor
type Simulation struct {
	EStorage EntityStorage
	Executor SystemExecutor
	Frame    *SimulationFrame

	eid EntityId
}

type SimulationFrame struct {
	Sim           *Simulation
	Delta         float64 // time delta
	LastFrameTime uint32
	Data          map[string]interface{} // data is a map: {string: any}
}

// / Get named Data key `namedKey` value of Frame,
// / return the key mapping value
func WithFrameData[T any](frame *SimulationFrame, namedKey string) T {
	return frame.Data[namedKey].(T)
}

// / Set the value to Data
func (s *SimulationFrame) Set(namedKey string, value interface{}) {
	s.Data[namedKey] = value
}

func NewSimulation(storage EntityStorage, executor SystemExecutor) *Simulation {
	sim := &Simulation{
		EStorage: storage,
		Executor: executor,
		eid:      1,
	}

	sim.Frame = &SimulationFrame{
		Sim:           sim,
		Delta:         0.0,
		LastFrameTime: 0,
		Data:          map[string]interface{}{},
	}

	return sim
}

func NewSimpleSimulation() *Simulation {
	return &Simulation{
		// EStorage has type EntityStorage
		// a interface contains 7 methods
		EStorage: NewEntitySimpleStorage(),
		Executor: NewSequentialSystemExecutor(),
	}
}

func (s *Simulation) AddEntity(cpns ...interface{}) EntityId {
	id := s.eid
	// add one, increment
	s.eid += 1
	s.EStorage.AddEnt(id, cpns...)
	return id
}

func (s *Simulation) DeleteEntity(id EntityId) {
	s.EStorage.DeleteEnt(id)
}

func (s *Simulation) GetComponent(id EntityId, cpn interface{}) bool {
	result := s.EStorage.GetComponent(id, reflect.TypeOf(cpn))
	// no component
	if result == nil {
		return false
	}
	// TODO: what is this line
	reflect.ValueOf(cpn).Elem().Set(reflect.ValueOf(result).Elem())

	return true
}

func (s *Simulation) RemoveComponent(id EntityId, cpn interface{}) {
	cpnType := reflect.TypeOf(cpn)
	if cpnType.Kind() == reflect.Struct {
		cpnType = reflect.PointerTo(cpnType)
	} else if cpnType.Kind() != reflect.Ptr || cpnType.Elem().Kind() != reflect.Struct {
		return
	}

	s.EStorage.RemoveComponent(id, cpnType)
}

func (s *Simulation) AddComponent(id EntityId, cpn interface{}) {
	s.EStorage.AddComponent(id, cpn)
}

func (s *Simulation) Setup() error {
	return s.Executor.Setup(s)
}

func (s *Simulation) Update() {
	s.Executor.Update(s.Frame)
}

func (s *Simulation) Render() {
	start := time.Now().UnixMicro()
	s.Executor.Render(s.Frame)
	s.Frame.LastFrameTime = uint32(time.Now().UnixMicro() - start)
}
